%include "pm.inc"

jmp LABEL_BEGIN

[SECTION .gdt]
; GDT描述符
; 每一项的格式: 
;   数据段/代码段描述符：       段基址， 段界限，   属性
;   门描述符：                段偏移， 段选择符，  属性,    参数个数
LABEL_GDT:              Descriptor      0,              0,                      0                       ; 空描述符
LABEL_DESC_CODE32:      Descriptor      0,              SegCode32Len - 1,       DA_C + DA_32            ; 进入保护模式段描述符
LABEL_DESC_VIDEO:       Descriptor      0B8000h,        0ffffh,                 DA_DRW                  ; 显存地址段（可读可写数据段）
LABEL_DESC_CALL_GATE:   GateDesc        0,              SelectorCodeGate32,     DA_386CGate,    0       ; 调用门描述符:32位调用门，1000 1100
LABEL_DESC_CODEGATE32:  Descriptor      0,              SegCodeGate32Len - 1,   DA_C + DA_32            ; 调用门的目标代码段描述符（非一致代码段）


; GDT结束

GdtLen equ $ - LABEL_GDT    ; GDT长度
GdtPtr dw GdtLen - 1        ; GDT界限
       dd 0                 ; GDT基地址


; GDT段选择符
SelectorCode32          equ     LABEL_DESC_CODE32 - LABEL_GDT
SelectorCallGate        equ     LABEL_DESC_CALL_GATE - LABEL_GDT
SelectorVideo           equ     LABEL_DESC_VIDEO - LABEL_GDT
SelectorCodeGate32      equ     LABEL_DESC_CODEGATE32 - LABEL_GDT

; End of GDT SECTION


[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, 0100h ; 初始化各个段，sp设置为该值有啥讲究？

    ; 初始化32位代码段描述符
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODE32               ; 实模式下，线性地址=段基址*16+偏移
    mov word [LABEL_DESC_CODE32 + 2], ax    ; 将eax寄存器（也就是线性地址）的低16位写入到代码段描述符的第2 3字节（以第0字节开始）
    shr eax, 16                             ; 获取线性地址的高16位，低16位在上面的步骤已经使用
    mov byte [LABEL_DESC_CODE32 + 4], al    ; 将线性地址的高16位中的低8位写入到段描述符的第4字节
    mov byte [LABEL_DESC_CODE32 + 7], ah    ; 将线性地址的高16位中的高8位写入到段描述符的第7字节


    ; 初始化32位调用门对应的目标代码段描述符
    xor eax, eax
    mov ax, cs
    shl eax, 4
    add eax, LABEL_SEG_CODEGATE32               ; 实模式下，线性地址=段基址*16+偏移
    mov word [LABEL_DESC_CODEGATE32 + 2], ax    ; 将eax寄存器（也就是线性地址）的低16位写入到代码段描述符的第2 3字节（以第0字节开始）
    shr eax, 16                                 ; 获取线性地址的高16位，低16位在上面的步骤已经使用
    mov byte [LABEL_DESC_CODEGATE32 + 4], al    ; 将线性地址的高16位中的低8位写入到段描述符的第4字节
    mov byte [LABEL_DESC_CODEGATE32 + 7], ah    ; 将线性地址的高16位中的高8位写入到段描述符的第7字节

    ; 初始化调用门描述符，偏移为0，因此不需要重新赋值
    ;xor eax, eax
    ;mov ax, SelectorCode32  ; 段选择符
    ;mov word [LABEL_DESC_CODE_GATE + 2], ax

    ; 为加载GDTR做准备
    xor eax, eax
    mov ax, ds
    shl eax, 4
    add eax, LABEL_GDT  ; gdt基地址 ---> eax
    mov dword [GdtPtr + 2], eax ; gdt基地址 ---> [GdtPtr + 2]

    ; 加载GDTR
    lgdt [GdtPtr]

    ; 关中断 保护模式下的中断处理机制和实模式下不一样，先简单关中断处理
    cli

    ; 打开地址线A20，历史原因，打开它才能访问超过1M的地址空间
    in al, 92h
    or al, 00000010b
    out 92h, al

    ; 准备切换到保护模式
    mov eax, cr0
    or eax, 1       ; CR0的最低位是PE，该值为1时表示启动分段保护模式，开启PE=1模式后，必须立马使用跳转命令，例如下面的jmp命令
    mov cr0, eax

    jmp dword SelectorCode32:0
    ;call SelectorCallGate:0

; End of [SECTION .s16]

[SECTION .code32]
[BITS 32]
LABEL_SEG_CODE32:
    mov ax, SelectorVideo
    mov gs, ax  ; 视频段选择子

    mov edi, (80 * 11 + 0) * 2     ; 屏幕第11行，第79列
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov al, 'P'
    mov [gs:edi], ax

    call SelectorCallGate:0

    jmp $

SegCode32Len equ $ - LABEL_SEG_CODE32
; End of SECTION .code32

[SECTION .codeGate32]
[BITS 32]
LABEL_SEG_CODEGATE32:
    mov ax, SelectorVideo
    mov gs, ax  ; 视频段选择子

    mov edi, (80 * 12 + 0) * 2     ; 屏幕第11行，第79列
    mov ah, 0ch     ; 0000 黑底， 000C 红字
    mov al, 'G'
    mov [gs:edi], ax

    retf

SegCodeGate32Len equ $ - LABEL_SEG_CODEGATE32
; End of SECTION .code32